<?php
/**
 * Security Middleware
 * Implements additional security measures and anti-brute force protection
 */

namespace app\api\middleware;

use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use support\Log;
use support\Cache;

class SecurityMiddleware implements MiddlewareInterface
{
    /**
     * Security configurations
     */
    private $securityConfig;
    
    public function __construct()
    {
        $this->securityConfig = config('security.security', [
            'max_failed_attempts' => 5,
            'lockout_duration' => 1800,
            'suspicious_activity_threshold' => 10,
            'block_duration' => 3600,
            'enable_ip_blocking' => true,
            'enable_user_agent_check' => true
        ]);
    }

    /**
     * Process the request and apply security measures
     */
    public function process(Request $request, callable $handler): Response
    {
        $clientIp = $request->getRealIp();
        $userAgent = $request->header('User-Agent', '');
        
        // Check if IP is blocked
        if ($this->isIpBlocked($clientIp)) {
            return $this->blockedResponse('IP address is temporarily blocked');
        }
        
        // Check for suspicious activity patterns
        if ($this->isSuspiciousActivity($clientIp, $userAgent)) {
            $this->blockIp($clientIp, 'Suspicious activity detected');
            return $this->blockedResponse('Suspicious activity detected');
        }
        
        // Check for brute force attempts on auth endpoints
        if ($this->isBruteForceAttempt($clientIp, $request)) {
            return $this->blockedResponse('Too many failed attempts. Please try again later.');
        }
        
        // Process the request
        $response = $handler($request);
        
        // Handle failed login attempts if recorded by controller
        if (isset($request->failed_login_attempt)) {
            $this->recordFailedAttempt(
                $request->failed_login_attempt['ip'],
                $request->failed_login_attempt['username']
            );
        }
        
        // Add security headers to response
        return $this->addSecurityHeaders($response);
    }
    
    /**
     * Check if IP is blocked
     */
    private function isIpBlocked(string $ip): bool
    {
        if (!$this->securityConfig['enable_ip_blocking']) {
            return false;
        }
        
        $blockKey = 'blocked_ip_' . str_replace(':', '_', $ip);
        $blocked = Cache::store('file')->get($blockKey);
        
        if ($blocked && $blocked['expires'] > time()) {
            return true;
        }
        
        // Remove expired block
        if ($blocked && $blocked['expires'] <= time()) {
            Cache::store('file')->delete($blockKey);
        }
        
        return false;
    }
    
    /**
     * Block an IP address
     */
    private function blockIp(string $ip, string $reason = 'Security violation'): void
    {
        $blockKey = 'blocked_ip_' . str_replace(':', '_', $ip);
        $blockData = [
            'ip' => $ip,
            'reason' => $reason,
            'blocked_at' => time(),
            'expires' => time() + $this->securityConfig['block_duration']
        ];
        
        Cache::store('file')->set($blockKey, $blockData, $this->securityConfig['block_duration']);
        
        Log::warning('IP blocked', $blockData);
    }
    
    /**
     * Check for suspicious activity patterns
     */
    private function isSuspiciousActivity(string $ip, string $userAgent): bool
    {
        $activityKey = 'activity_' . str_replace(':', '_', $ip);
        $currentMinute = floor(time() / 60);
        $minuteKey = $activityKey . '_' . $currentMinute;
        
        // Count requests in current minute
        $requestCount = Cache::store('file')->get($minuteKey);
        $requestCount = is_null($requestCount) ? 0 : (int)$requestCount;
        
        if ($requestCount >= $this->securityConfig['suspicious_activity_threshold']) {
            return true;
        }
        
        // Increment counter
        if ($requestCount === 0) {
            Cache::store('file')->set($minuteKey, 1, 120);
        } else {
            Cache::store('file')->set($minuteKey, $requestCount + 1, 120);
        }
        
        // Check for suspicious user agent patterns
        if ($this->securityConfig['enable_user_agent_check']) {
            if ($this->isSuspiciousUserAgent($userAgent)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Check for suspicious user agent patterns
     */
    private function isSuspiciousUserAgent(string $userAgent): bool
    {
        $suspiciousPatterns = [
            'bot', 'crawler', 'spider', 'scraper', 'curl', 'wget',
            'python', 'java', 'perl', 'ruby', 'go-http-client'
        ];
        
        $userAgentLower = strtolower($userAgent);
        
        foreach ($suspiciousPatterns as $pattern) {
            if (strpos($userAgentLower, $pattern) !== false) {
                Log::warning('Suspicious user agent detected', [
                    'user_agent' => $userAgent,
                    'pattern' => $pattern
                ]);
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Check for brute force attempts
     */
    private function isBruteForceAttempt(string $ip, Request $request): bool
    {
        if (!$this->isAuthEndpoint($request)) {
            return false;
        }
        
        $failedAttemptsKey = 'failed_attempts_' . str_replace(':', '_', $ip);
        $lockoutKey = 'lockout_' . str_replace(':', '_', $ip);
        
        // Check if IP is currently locked out
        $lockoutData = Cache::store('file')->get($lockoutKey);
        if ($lockoutData && $lockoutData['expires'] > time()) {
            return true;
        }
        
        // Remove expired lockout
        if ($lockoutData && $lockoutData['expires'] <= time()) {
            Cache::store('file')->delete($lockoutKey);
            Cache::store('file')->delete($failedAttemptsKey);
        }
        
        return false;
    }
    
    /**
     * Record failed login attempt
     */
    public function recordFailedAttempt(string $ip, string $username = ''): void
    {
        $failedAttemptsKey = 'failed_attempts_' . str_replace(':', '_', $ip);
        $lockoutKey = 'lockout_' . str_replace(':', '_', $ip);
        
        $failedAttempts = Cache::store('file')->get($failedAttemptsKey);
        $failedAttempts = is_null($failedAttempts) ? 0 : (int)$failedAttempts;
        $failedAttempts++;
        
        Cache::store('file')->set($failedAttemptsKey, $failedAttempts, $this->securityConfig['lockout_duration']);
        
        Log::warning('Failed login attempt recorded', [
            'ip' => $ip,
            'username' => $username,
            'attempts' => $failedAttempts
        ]);
        
        // Lock out IP if max attempts reached
        if ($failedAttempts >= $this->securityConfig['max_failed_attempts']) {
            $lockoutData = [
                'ip' => $ip,
                'attempts' => $failedAttempts,
                'locked_at' => time(),
                'expires' => time() + $this->securityConfig['lockout_duration']
            ];
            
            Cache::store('file')->set($lockoutKey, $lockoutData, $this->securityConfig['lockout_duration']);
            
            Log::warning('IP locked out due to failed attempts', $lockoutData);
        }
    }
    
    /**
     * Check if request is to an authentication endpoint
     */
    private function isAuthEndpoint(Request $request): bool
    {
        $authPaths = ['/api/auth/login', '/api/auth/register', '/api/users/login', '/api/users/register', '/api/admin/login'];
        $path = $request->path();
        
        foreach ($authPaths as $authPath) {
            if (strpos($path, $authPath) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Generate blocked response
     */
    private function blockedResponse(string $message): Response
    {
        return json([
            'code' => 403,
            'msg' => $message,
            'data' => [
                'blocked' => true,
                'retry_after' => 3600 // 1 hour
            ]
        ], 403)->withHeaders([
            'Retry-After' => 3600,
            'X-Blocked' => 'true'
        ]);
    }
    
    /**
     * Add security headers to response
     */
    private function addSecurityHeaders(Response $response): Response
    {
        $securityHeaders = [
            'X-Content-Type-Options' => 'nosniff',
            'X-Frame-Options' => 'DENY',
            'X-XSS-Protection' => '1; mode=block',
            'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
            'Referrer-Policy' => 'strict-origin-when-cross-origin',
            'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()',
            'X-Robots-Tag' => 'noindex, nofollow'
        ];
        
        foreach ($securityHeaders as $header => $value) {
            $response->withHeader($header, $value);
        }
        
        return $response;
    }
}
